package de.ret_world.openrpgstudio.editor;

import buoy.event.CommandEvent;
import buoy.event.WindowClosingEvent;
import buoy.widget.*;
import de.ret_world.openrpgstudio.editor.gui.AudioPreviewWidget;
import de.ret_world.openrpgstudio.editor.gui.DatabaseDialog;
import de.ret_world.openrpgstudio.editor.gui.NewProjectDialog;
import de.ret_world.openrpgstudio.editor.gui.OptionsDialog;
import de.ret_world.openrpgstudio.editor.helpers.Internationalization;
import de.ret_world.openrpgstudio.editor.helpers.ScriptTools;
import de.ret_world.openrpgstudio.editor.plugininterfaces.MainWindowPlugin;
import de.ret_world.openrpgstudio.editor.plugininterfaces.ToolPlugin;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Properties;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import org.java.plugin.PluginLifecycleException;
import org.java.plugin.boot.Application;
import org.java.plugin.boot.ApplicationPlugin;
import org.java.plugin.registry.Extension;
import org.java.plugin.registry.Extension.Parameter;
import org.java.plugin.registry.ExtensionPoint;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.util.ExtendedProperties;
import org.luaj.vm2.lib.LibFunction;

/**
 * This is the main application class
 *
 * @author Emu
 */
public class Main extends ApplicationPlugin implements Application {

    /**
     * This plug-in ID.
     */
    public static final String PLUGIN_ID = "de.ret_world.openrpgstudio.editor";
    //Variables
    private BFrame window;
    private Configuration config;
    private BToolBar toolbar;
    private Project project;
    private ArrayList<MainWindowPlugin> mwPlugins;

    @Override
    protected Application initApplication(ExtendedProperties ep, String[] args) {
        Path dataFolder = new File(ep.getProperty("dataFolder", "." + File.separator + "data")).toPath();
        dataFolder = dataFolder.normalize();
        log.info("data folder - " + dataFolder);
        try {
            Files.createDirectories(dataFolder);
        } catch (IOException ex) {
            log.error(ex);
        }

        Configuration.dataFolder = dataFolder;

        //Loading config files
        config = Configuration.getInstance(PLUGIN_ID);

        //Load languages.lst
        ArrayList<String> languages = new ArrayList<>();
        URL langs = getManager().getPluginClassLoader(getDescriptor()).getResource("languages.lst");
        try {
            Path langPath = Paths.get(langs.toURI());
            try (BufferedReader br = Files.newBufferedReader(langPath, Charset.defaultCharset())) {

                String tmp = null;
                while ((tmp = br.readLine()) != null) {
                    languages.add(tmp);
                }
            }
        } catch (IOException | URISyntaxException ex) {
            log.error(ex);
        }

        StringBuilder sb = new StringBuilder();
        for (String s : languages) {
            sb.append(s).append(",");
        }
        config.setProperty("languages.lst", sb.substring(0, sb.length() - 1));

        reloadInternationalization();

        mwPlugins = new ArrayList<>();

        //Set control Icons for AudioPreviewWidget
        AudioPreviewWidget.iconStart = new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("control.png"));
        AudioPreviewWidget.iconStop = new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("control-stop-square.png"));

        return this;
    }

    @Override
    protected void doStart() throws Exception {
    }

    @Override
    protected void doStop() throws Exception {
    }

    @Override
    public void startApplication() throws Exception {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                initGUI();
            }
        });
    }

    private void initGUI() {
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            log.error(ex);
        }
        //</editor-fold>

        //Generating Window
        int x = Integer.parseInt(config.getProperty("window.loc.x", "100"));
        int y = Integer.parseInt(config.getProperty("window.loc.y", "100"));
        int w = Integer.parseInt(config.getProperty("window.size.w", "1300"));
        int h = Integer.parseInt(config.getProperty("window.size.h", "800"));
        boolean m = Boolean.parseBoolean(config.getProperty("window.maximized", "false"));

        Internationalization intn = Internationalization.getInstance();

        window = new BFrame("OpenRPGStudio - Editor");
        window.setBounds(new Rectangle(x, y, w, h));
        window.setMaximized(m);
        window.setVisible(true);
        window.addEventLink(WindowClosingEvent.class, this, "closeApp");

        //Window GUI
        BorderContainer bc = new BorderContainer();

        toolbar = new BToolBar(BToolBar.HORIZONTAL);
        BButton btn = new BButton(intn.getString("MENU.NEW"));
        btn.setTextPosition(BButton.SOUTH);
        btn.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("folder_24.png")));
        btn.addEventLink(CommandEvent.class, this, "newProjectUI");
        //btn.setFont(new Font("Arial", Font.PLAIN, 10));
        toolbar.add(btn);
        btn = new BButton(intn.getString("MENU.OPEN"));
        btn.setTextPosition(BButton.SOUTH);
        btn.addEventLink(CommandEvent.class, this, "openProjectUI");
        btn.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("folder-open_24.png")));
        toolbar.add(btn);
        toolbar.add(new BSeparator(BSeparator.VERTICAL));
        if (Project.isProjectLoaded()) {
            btn = new BButton(intn.getString("MENU.DATABASE"));
            btn.setTextPosition(BButton.SOUTH);
            btn.addEventLink(CommandEvent.class, this, "openDatabaseUI");
            btn.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("database_24.png")));
            toolbar.add(btn);
        }
        bc.add(toolbar, BorderContainer.NORTH, new LayoutInfo(LayoutInfo.NORTHWEST, LayoutInfo.HORIZONTAL, new Insets(5, 5, 5, 5), null));

        BTabbedPane tabbed = new BTabbedPane();

        if (Project.isProjectLoaded()) {
            //Plugin Extension Point "MainWindow"
            ExtensionPoint toolExtPoint = getManager().getRegistry().getExtensionPoint(getDescriptor().getId(), "MainWindow");
            for (Extension ext : toolExtPoint.getConnectedExtensions()) {
                if (getManager().isPluginEnabled(ext.getDeclaringPluginDescriptor())) {
                    BorderContainer pluginContainer = new BorderContainer();

                    //initialize the plugin
                    MainWindowPlugin plugin = null;

                    try {
                        // Activate plug-in that declares extension.
                        getManager().activatePlugin(ext.getDeclaringPluginDescriptor().getId());
                        reloadInternationalization();
                        // Get plug-in class loader.
                        ClassLoader classLoader = getManager().getPluginClassLoader(ext.getDeclaringPluginDescriptor());
                        // Load Tool class.
                        Class<?> clazz = classLoader.loadClass(ext.getParameter("class").valueAsString());
                        // Create Tool instance.
                        plugin = (MainWindowPlugin) clazz.newInstance();
                        // Initialize class instance according to interface contract.
                        plugin.init(pluginContainer, window);

                        mwPlugins.add(plugin);

                        tabbed.add(pluginContainer, ext.getParameter("name").valueAsString());
                    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | PluginLifecycleException ex) {
                        log.error(ex);
                    }
                }
            }
        }

        bc.add(BOutline.createEtchedBorder(tabbed, false), BorderContainer.CENTER, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH));

        window.setContent(bc);

        window.setMenuBar(initMenus());
        refreshWindowProject();
    }

    private BMenuBar initMenus() {
        Internationalization intn = Internationalization.getInstance();

        BMenuBar menubar = new BMenuBar();

        BMenu menu = new BMenu(intn.getString("MENU.FILE"));
        BMenuItem mi = new BMenuItem(intn.getString("MENU.NEW"));
        mi.addEventLink(CommandEvent.class, this, "newProjectUI");
        mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("folder.png")));
        mi.setShortcut(new Shortcut(KeyEvent.VK_N, Shortcut.CTRL_MASK));
        menu.add(mi);
        mi = new BMenuItem(intn.getString("MENU.OPEN"));
        mi.addEventLink(CommandEvent.class, this, "openProjectUI");
        mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("folder-open.png")));
        mi.setShortcut(new Shortcut(KeyEvent.VK_O, Shortcut.CTRL_MASK));
        menu.add(mi);
        menu.add(new BSeparator());
        mi = new BMenuItem(intn.getString("MENU.CLOSE"));
        mi.addEventLink(CommandEvent.class, this, "closeApp");
        mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("cross-octagon-frame.png")));
        mi.setShortcut(new Shortcut(KeyEvent.VK_Q, Shortcut.CTRL_MASK));
        menu.add(mi);

        menubar.add(menu);

        menu = new BMenu(intn.getString("MENU.TOOLS"));

        if (Project.isProjectLoaded()) {
            mi = new BMenuItem(intn.getString("MENU.DATABASE"));
            mi.addEventLink(CommandEvent.class, this, "openDatabaseUI");
            mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("database.png")));
            mi.setShortcut(new Shortcut(KeyEvent.VK_D, Shortcut.CTRL_MASK));
            menu.add(mi);

            //Plugin Extension Point "Tool"
            ExtensionPoint toolExtPoint = getManager().getRegistry().getExtensionPoint(getDescriptor().getId(), "Tool");
            for (Extension ext : toolExtPoint.getConnectedExtensions()) {
                if (getManager().isPluginEnabled(ext.getDeclaringPluginDescriptor())) {

                    mi = new BMenuItem();
                    mi.setText(ext.getParameter("name").valueAsString());

                    BButton btn = new BButton();
                    btn.setText(ext.getParameter("name").valueAsString());
                    btn.setTextPosition(BButton.SOUTH);

                    //initialize the plugin
                    ToolPlugin plugin = null;

                    try {
                        // Activate plug-in that declares extension.
                        getManager().activatePlugin(ext.getDeclaringPluginDescriptor().getId());
                        reloadInternationalization();
                        // Get plug-in class loader.
                        ClassLoader classLoader = getManager().getPluginClassLoader(ext.getDeclaringPluginDescriptor());
                        // Load Tool class.
                        Class<?> clazz = classLoader.loadClass(ext.getParameter("class").valueAsString());
                        // Create Tool instance.
                        plugin = (ToolPlugin) clazz.newInstance();
                        // Initialize class instance according to interface contract.
                        plugin.init(mi, btn, window);

                        //Getting icons
                        Parameter sicon = ext.getParameter("icon16");
                        if (sicon != null) {
                            URL uicon = classLoader.getResource(sicon.valueAsString());
                            if (uicon != null) {
                                Icon picon = new ImageIcon(uicon);
                                mi.setIcon(picon);
                            }
                        }
                        sicon = ext.getParameter("icon24");
                        if (sicon != null) {
                            URL uicon = classLoader.getResource(sicon.valueAsString());
                            if (uicon != null) {
                                Icon picon = new ImageIcon(uicon);
                                btn.setIcon(picon);
                            }
                        }
                        //Getting shortcut
                        Shortcut sc = plugin.getShortcut();
                        if (sc != null) {
                            mi.setShortcut(sc);
                        }

                        menu.add(mi);
                        toolbar.add(btn);
                    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | PluginLifecycleException ex) {
                        log.error(ex);
                    }
                }
            }
        }

        menu.add(new BSeparator());
        mi = new BMenuItem(intn.getString("MENU.APPOPTIONS"));
        mi.addEventLink(CommandEvent.class, this, "openOptionsUI");
        mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("wrench-screwdriver.png")));
        mi.setShortcut(new Shortcut(KeyEvent.VK_O, Shortcut.CTRL_MASK | Shortcut.SHIFT_MASK));
        menu.add(mi);

        menubar.add(menu);

        menu = new BMenu(intn.getString("MENU.HELP"));
        mi = new BMenuItem(intn.getString("MENU.ABOUT"));
        mi.addEventLink(CommandEvent.class, this, "openAboutUI");
        mi.setIcon(new ImageIcon(getManager().getPluginClassLoader(getDescriptor()).getResource("universal.png")));
        menu.add(mi);

        menubar.add(menu);

        return menubar;
    }

    private void storeConfig() {
        log.info("Saving configuration files");

        config.setProperty("window.loc.x", "" + window.getBounds().x);
        config.setProperty("window.loc.y", "" + window.getBounds().y);
        config.setProperty("window.size.w", "" + window.getBounds().width);
        config.setProperty("window.size.h", "" + window.getBounds().height);
        config.setProperty("window.maximized", "" + window.isMaximized());

        for (MainWindowPlugin p : mwPlugins) {
            p.save();
        }

        config.storeConfig();
    }

    public void reloadInternationalization() {
        Properties lang = new Properties();
        for (PluginDescriptor descr : getManager().getRegistry().getPluginDescriptors()) {
            //Initialize Internationalization
            if (getManager().isPluginActivated(descr)) {
                log.info("Attempting to load plugin internationalization data for: " + descr.getUniqueId());
                try {
                    URL langURL = getManager().getPluginClassLoader(descr).getResource(config.getProperty("window.language", "eng") + ".properties");
                    if (langURL != null) {
                        lang.load(langURL.openStream());
                    } else {
                        log.warn("No internationalization for language \"" + config.getProperty("window.language", "eng") + "\" found in plugin " + descr.getUniqueId());
                        if (Boolean.valueOf(config.getProperty("window.language.replacenotfound", "true"))) {
                            langURL = getManager().getPluginClassLoader(descr).getResource("eng.properties");
                            if (langURL != null) {
                                lang.load(langURL.openStream());
                            }
                        }
                    }
                } catch (IOException ex) {
                    log.error(ex);
                }
            }
        }
        Internationalization intn = Internationalization.getInstance();
        intn.loadStringTable(lang);
        log.info("Loaded plugin internationalization data: " + intn.getString("LANGUAGE.NAME") + " v" + intn.getString("LANGUAGE.VERSION"));
        reloadLibraries();
    }

    private void reloadLibraries() {
        //Plugin Extension Point "Library"
        if (Project.isProjectLoaded()) {
            ExtensionPoint toolExtPoint = getManager().getRegistry().getExtensionPoint(Main.PLUGIN_ID, "Library");
            for (Extension ext : toolExtPoint.getConnectedExtensions()) {
                //initialize the plugin
                if (getManager().isPluginEnabled(ext.getDeclaringPluginDescriptor())) {
                    try {
                        // Activate plug-in that declares extension.
                        getManager().activatePlugin(ext.getDeclaringPluginDescriptor().getId());
                        // Get plug-in class loader.
                        ClassLoader classLoader = getManager().getPluginClassLoader(ext.getDeclaringPluginDescriptor());
                        // Load Tool class.
                        Class<?> clazz = classLoader.loadClass(ext.getParameter("class").valueAsString());
                        // Create Tool instance.
                        LibFunction lualib = (LibFunction) clazz.newInstance();
                        project.getScriptRegister().registerLibrary(ext.getParameter("name").valueAsString(), lualib);
                        log.info("Loaded Lua Library: " + ext.getParameter("name").valueAsString());
                    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | PluginLifecycleException ex) {
                        log.error(ex);
                    }
                }
            }
            ScriptTools.getInstance().executeStartScript();
        }
    }

    public void refreshWindowProject() {
        if (project != null) {
            window.setTitle("OpenRPGStudio - Editor :: " + project.getConfig().getProperty("project.name") + " (" + project.getConfig().getProperty("game.name") + ")");
        } else {
            window.setTitle("OpenRPGStudio - Editor");
        }
    }

    private void refreshGUI() {
        storeConfig();
        window.dispose();
        reloadInternationalization();
        //refreshWindowProject();
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                initGUI();
            }
        });
    }

    /*
     * EVENTS
     */
    /**
     * Disposes the window and shuts down the application
     */
    public void closeApp() {
        storeConfig();

        window.dispose();
        getManager().shutdown();
    }

    private void openOptionsUI() {
        String oldLang = config.getProperty("window.language", "eng");
        OptionsDialog od = new OptionsDialog(window, this);
        od.showDialog(getManager().getPluginClassLoader(getDescriptor()));
        refreshGUI();
    }

    private void openAboutUI() {
        Internationalization intn = Internationalization.getInstance();
        JOptionPane.showMessageDialog(window.getComponent(), "OpenRPGStudio\nVersion: " + getDescriptor().getVersion().toString() + "\nwww.ret-world.de\n" + intn.getString("ABOUT.LICENSE"), intn.getString("MENU.ABOUT"), JOptionPane.INFORMATION_MESSAGE);
    }

    private void newProjectUI() {
        NewProjectDialog npd = new NewProjectDialog(window);
        Properties prop = npd.showDialog();
        if (prop != null) {
            log.info("Creating new Project: " + prop.getProperty("project.name"));
            project = Project.newProject(Paths.get(prop.getProperty("project.path")));
            project.getConfig().setProperty("project.name", prop.getProperty("project.name"));
            project.getConfig().setProperty("game.name", prop.getProperty("game.name"));

            try {
                project.save();
            } catch (IOException ex) {
                log.error(ex);
            }
            refreshGUI();
        }
    }

    private void openProjectUI() {
        final Internationalization intn = Internationalization.getInstance();
        BFileChooser fc = new BFileChooser(BFileChooser.OPEN_FILE, intn.getString("MENU.OPENPROJECT"));
        fc.setFileFilter(new FileFilter() {

            @Override
            public boolean accept(File f) {
                if (f.isDirectory() || f.getName().endsWith(".rpg")) {
                    return true;
                }
                return false;
            }

            @Override
            public String getDescription() {
                return intn.getString("MENU.FILEDESCR") + " (*.rpg)";
            }
        });
        fc.setDirectory(Paths.get(config.getProperty("project.path", "Projects")).toFile());
        if (fc.showDialog(window)) {
            Path projectPath = fc.getSelectedFile().toPath().getParent();
            log.info("Loading Project from: " + projectPath);
            project = Project.loadProject(projectPath, getManager());
            refreshGUI();
        }
    }

    private void openDatabaseUI() {
        DatabaseDialog dd = new DatabaseDialog(window, this);
        dd.showDialog(getManager().getPluginClassLoader(getDescriptor()));
    }
}
